package level.editor.control.main;

import java.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.event.KeyEvent;
import java.awt.event.WindowFocusListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import level.editor.control.Handler;
import level.editor.control.collision.CollisionHandler;
import level.editor.control.object.ObjectHandler;
import level.editor.control.select.SelectHandler;
import level.editor.control.tile.TileHandler;
import level.editor.view.Window;
import level.editor.view.action.SelectHandlerAction;
import level.editor.view.action.file.CloseAction;
import level.editor.view.desktop.Desktop;
import level.model.State;
import level.model.utility.StateHandler;
import level.model.utility.StateLoader;

public class MainTabbed extends JTabbedPane
{

	// Called from CenterPanel
	public MainTabbed(Desktop desktop)
	{
		super();
		this.desktop = desktop;
		init();
	}

	public Main getSelectedMain()
	{
		if (getSelectedComponent() != null)
			return ((MainScroll) getSelectedComponent()).getMain();
		else
			return null;
	}

	public void setHandler(Handler handler)
	{
		if (handler instanceof TileHandler)
		{
			tileHandler = (TileHandler) handler;
		}

		if (handler instanceof CollisionHandler)
		{
			collisionHandler = (CollisionHandler) handler;
		}

		if (handler instanceof SelectHandler)
		{
			selectHandler = (SelectHandler) handler;
		}

		if (handler instanceof ObjectHandler)
		{
			objectHandler = (ObjectHandler) handler;
		}
	}

	public Handler getHandler(String type)
	{
		if (type.equals(SelectHandlerAction.TILE_HANDLER))
		{
			return tileHandler;
		}
		else if (type.equals(SelectHandlerAction.COLLISION_HANDLER))
		{
			return collisionHandler;
		}
		else if (type.equals(SelectHandlerAction.SELECT_HANDLER))
		{
			return selectHandler;
		}
		else if (type.equals(SelectHandlerAction.OBJECT_HANDLER))
		{
			return objectHandler;
		}
		return null;
	}

	public void newState()
	{
		Main main = new Main(StateHandler.defaultState(new State(),
				produceNumber()));
		main.init(desktop.getSize());
		MainScroll mainScroll = new MainScroll(main);
		addTab(main.getName(), mainScroll);
		setSelectedComponent(mainScroll);
	}

	public void open()
	{
		State state = StateLoader.open();
		if (state != null)
		{
			Main main = new Main(state);
			main.init(desktop.getSize());
			main.paintFromState();
			main.setDirty(Main.NOT_DIRTY);

			MainScroll mainScroll = new MainScroll(main);
			addTab(main.getName(), mainScroll);
			setSelectedComponent(mainScroll);
		}
	}

	public CloseAction getCloseAction()
	{
		for (AbstractAction action : desktop.getActions())
		{
			if (action instanceof CloseAction)
			{
				return (CloseAction) action;
			}
		}
		return null;
	}

	@Override
	public void addTab(String name, Component component)
	{
		super.addTab(name, component);
		setTabComponentAt(getTabCount() - 1, new MainTabComponent(this));
	}

	private void init()
	{
		setFocusable(false);
		newState();
		initHandlers();
		initSelectHandler();
		// initDragAndDrop();
	}

	@SuppressWarnings("unused")
	private void initDragAndDrop()
	{
		final DragSourceListener dsl = new DragSourceListener()
		{
			public void dragEnter(DragSourceDragEvent e)
			{
				e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
			}

			public void dragExit(DragSourceEvent e)
			{
				e.getDragSourceContext()
						.setCursor(DragSource.DefaultMoveNoDrop);
				m_lineRect.setRect(0, 0, 0, 0);
			}

			public void dragOver(DragSourceDragEvent e)
			{
				TabTransferData data = getTabTransferData(e);
				if (data == null)
				{
					e.getDragSourceContext().setCursor(
							DragSource.DefaultMoveNoDrop);
					return;
				}

				e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
			}

			public void dragDropEnd(DragSourceDropEvent e)
			{
				m_lineRect.setRect(0, 0, 0, 0);
				// m_dragTabIndex = -1;
			}

			public void dropActionChanged(DragSourceDragEvent e)
			{
			}
		};

		final DragGestureListener dgl = new DragGestureListener()
		{
			public void dragGestureRecognized(DragGestureEvent e)
			{
				// System.out.println("dragGestureRecognized");

				Point tabPt = e.getDragOrigin();
				int dragTabIndex = indexAtLocation(tabPt.x, tabPt.y);
				if (dragTabIndex < 0)
				{
					return;
				}

				try
				{
					e.startDrag(DragSource.DefaultMoveDrop,
							new TabTransferable(MainTabbed.this, dragTabIndex),
							dsl);
				}
				catch (InvalidDnDOperationException idoe)
				{
					idoe.printStackTrace();
				}
			}
		};

		// dropTarget =
		new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE,
				new CDropTargetListener(), true);
		new DragSource().createDefaultDragGestureRecognizer(this,
				DnDConstants.ACTION_COPY_OR_MOVE, dgl);
		m_acceptor = new TabAcceptor()
		{
			public boolean isDropAcceptable(MainTabbed a_component, int a_index)
			{
				return true;
			}
		};
	}

	public TabAcceptor getAcceptor()
	{
		return m_acceptor;
	}

	public void setAcceptor(TabAcceptor a_value)
	{
		m_acceptor = a_value;
	}

	private TabTransferData getTabTransferData(DropTargetDropEvent a_event)
	{
		try
		{
			if (a_event.getTransferable() instanceof TabTransferData)
			{
				TabTransferData data = (TabTransferData) a_event
						.getTransferable().getTransferData(FLAVOR);
				return data;
			}
			else
				return null;
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return null;
	}

	private TabTransferData getTabTransferData(DropTargetDragEvent a_event)
	{
		try
		{
			if (a_event.getTransferable() instanceof TabTransferData)
			{
				TabTransferData data = (TabTransferData) a_event
						.getTransferable().getTransferData(FLAVOR);
				return data;
			}
			else
				return null;
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return null;
	}

	private TabTransferData getTabTransferData(DragSourceDragEvent a_event)
	{
		try
		{
			TabTransferData data = (TabTransferData) a_event
					.getDragSourceContext().getTransferable()
					.getTransferData(FLAVOR);
			return data;
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		return null;
	}

	class TabTransferable implements Transferable
	{
		private TabTransferData m_data = null;

		public TabTransferable(MainTabbed a_tabbedPane, int a_tabIndex)
		{
			m_data = new TabTransferData(MainTabbed.this, a_tabIndex);
		}

		public Object getTransferData(DataFlavor flavor)
		{
			return m_data;
			// return DnDTabbedPane.this;
		}

		public DataFlavor[] getTransferDataFlavors()
		{
			DataFlavor[] f = new DataFlavor[1];
			f[0] = FLAVOR;
			return f;
		}

		public boolean isDataFlavorSupported(DataFlavor flavor)
		{
			return flavor.getHumanPresentableName().equals(NAME);
		}
	}

	public class TabTransferData
	{
		private MainTabbed m_tabbedPane = null;
		private int m_tabIndex = -1;

		public TabTransferData()
		{
		}

		public TabTransferData(MainTabbed a_tabbedPane, int a_tabIndex)
		{
			m_tabbedPane = a_tabbedPane;
			m_tabIndex = a_tabIndex;
		}

		public MainTabbed getTabbedPane()
		{
			return m_tabbedPane;
		}

		public void setTabbedPane(MainTabbed pane)
		{
			m_tabbedPane = pane;
		}

		public int getTabIndex()
		{
			return m_tabIndex;
		}

		public void setTabIndex(int index)
		{
			m_tabIndex = index;
		}
	}

	class CDropTargetListener implements DropTargetListener
	{
		public void dragEnter(DropTargetDragEvent e)
		{
			DataFlavor[] flavor = e.getCurrentDataFlavors();
			if (!flavor[0].getHumanPresentableName().equals(NAME))
			{
				return;
			}

			if (isDragAcceptable(e))
			{
				e.acceptDrag(e.getDropAction());
			}
			else
			{
				e.rejectDrag();
			}
		}

		public void dragExit(DropTargetEvent e)
		{
			setBorder(null);
		}

		public void dropActionChanged(DropTargetDragEvent e)
		{
		}

		public void dragOver(final DropTargetDragEvent e)
		{
			TabTransferData data = getTabTransferData(e);
			DataFlavor[] flavor = e.getCurrentDataFlavors();
			if (!flavor[0].getHumanPresentableName().equals(NAME))
			{
				return;
			}

			if (getTabPlacement() == MainTabbed.TOP
					|| getTabPlacement() == MainTabbed.BOTTOM)
			{
				initTargetLeftRightLine(getTargetTabIndex(e.getLocation()),
						data);
			}
			else
			{
				initTargetTopBottomLine(getTargetTabIndex(e.getLocation()),
						data);
			}

			repaint();
		}

		public void drop(DropTargetDropEvent a_event)
		{
			DataFlavor[] flavor = a_event.getCurrentDataFlavors();
			if (!flavor[0].getHumanPresentableName().equals(NAME))
			{
				return;
			}

			if (isDropAcceptable(a_event))
			{
				convertTab(getTabTransferData(a_event),
						getTargetTabIndex(a_event.getLocation()));
				a_event.dropComplete(true);
			}
			else
			{
				a_event.dropComplete(false);
			}

			setBorder(null);
			repaint();
		}

		public boolean isDragAcceptable(DropTargetDragEvent e)
		{
			Transferable t = e.getTransferable();
			if (t == null)
			{
				return false;
			}

			DataFlavor[] flavor = e.getCurrentDataFlavors();

			if (!flavor[0].getHumanPresentableName().equals(NAME))
			{
				return false;
			}

			TabTransferData data = getTabTransferData(e);

			if (data != null && MainTabbed.this == data.getTabbedPane()
					&& data.getTabIndex() >= 0)
			{
				return true;
			}

			if (data != null && data.getTabbedPane() != null
					&& data.getTabbedPane() instanceof MainTabbed
					&& MainTabbed.this != data.getTabbedPane())
			{
				if (m_acceptor != null)
				{
					return m_acceptor.isDropAcceptable(data.getTabbedPane(),
							data.getTabIndex());
				}
			}

			return false;
		}

		public boolean isDropAcceptable(DropTargetDropEvent e)
		{
			Transferable t = e.getTransferable();
			if (t == null)
			{
				return false;
			}

			DataFlavor[] flavor = e.getCurrentDataFlavors();
			if (!flavor[0].getHumanPresentableName().equals(NAME))
			{
				return false;
			}

			TabTransferData data = getTabTransferData(e);

			if (data != null && MainTabbed.this == data.getTabbedPane()
					&& data.getTabIndex() >= 0)
			{
				return true;
			}

			if (data != null && data.getTabbedPane() != null
					&& data.getTabbedPane() instanceof MainTabbed
					&& MainTabbed.this != data.getTabbedPane())
			{
				if (m_acceptor != null)
				{
					return m_acceptor.isDropAcceptable(data.getTabbedPane(),
							data.getTabIndex());
				}
			}

			return false;
		}
	}

	private boolean m_hasGhost = true;

	public void setPaintGhost(boolean flag)
	{
		m_hasGhost = flag;
	}

	public boolean hasGhost()
	{
		return m_hasGhost;
	}

	private int getTargetTabIndex(Point a_point)
	{
		boolean isTopOrBottom = getTabPlacement() == MainTabbed.TOP
				|| getTabPlacement() == MainTabbed.BOTTOM;

		// if the pane is empty, the target index is always zero.
		if (getTabCount() == 0)
		{
			return 0;
		}

		for (int i = 0; i < getTabCount(); i++)
		{
			Rectangle r = getBoundsAt(i);
			if (isTopOrBottom)
			{
				r.setRect(r.x - r.width / 2, r.y, r.width, r.height);
			}
			else
			{
				r.setRect(r.x, r.y - r.height / 2, r.width, r.height);
			}

			if (r.contains(a_point))
			{
				return i;
			}
		}

		Rectangle r = getBoundsAt(getTabCount() - 1);
		if (isTopOrBottom)
		{
			int x = r.x + r.width / 2;
			r.setRect(x, r.y, getWidth() - x, r.height);
		}
		else
		{
			int y = r.y + r.height / 2;
			r.setRect(r.x, y, r.width, getHeight() - y);
		}

		return r.contains(a_point) ? getTabCount() : -1;
	}

	private void convertTab(TabTransferData a_data, int a_targetIndex)
	{

		MainTabbed source = a_data.getTabbedPane();
		int sourceIndex = a_data.getTabIndex();
		if (sourceIndex < 0)
		{
			return;
		}
		Component cmp = source.getComponentAt(sourceIndex);
		String str = source.getTitleAt(sourceIndex);
		Component tcmp = source.getTabComponentAt(sourceIndex);

		if (this != source)
		{
			source.remove(sourceIndex);

			if (a_targetIndex == getTabCount())
			{
				addTab(cmp.getName(), cmp);
				setTabComponentAt(getTabCount() - 1, new MainTabComponent(this));
			}
			else
			{
				if (a_targetIndex < 0)
				{
					a_targetIndex = 0;
				}

				insertTab(str, null, cmp, null, a_targetIndex);
				setTabComponentAt(a_targetIndex, new MainTabComponent(this));
			}

			setSelectedComponent(cmp);
			return;
		}
		if (a_targetIndex < 0 || sourceIndex == a_targetIndex)
		{
			return;
		}
		if (a_targetIndex == getTabCount())
		{
			source.safeRemove(sourceIndex);
			addTab(str, cmp);
			setTabComponentAt(getTabCount() - 1, tcmp);
			setSelectedIndex(getTabCount() - 1);
		}
		else if (sourceIndex > a_targetIndex)
		{
			source.safeRemove(sourceIndex);
			insertTab(str, null, cmp, null, a_targetIndex);
			setTabComponentAt(a_targetIndex, tcmp);
			setSelectedIndex(a_targetIndex);
		}
		else
		{
			source.safeRemove(sourceIndex);
			insertTab(str, null, cmp, null, a_targetIndex - 1);
			setTabComponentAt(a_targetIndex - 1, tcmp);
			setSelectedIndex(a_targetIndex - 1);
		}

	}

	private void initTargetLeftRightLine(int next, TabTransferData a_data)
	{
		if (next < 0)
		{
			m_lineRect.setRect(0, 0, 0, 0);
			return;
		} // if

		if ((a_data.getTabbedPane() == this)
				&& (a_data.getTabIndex() == next || next - a_data.getTabIndex() == 1))
		{
			m_lineRect.setRect(0, 0, 0, 0);
		}
		else if (getTabCount() == 0)
		{
			m_lineRect.setRect(0, 0, 0, 0);
			return;
		}
		else if (next == 0)
		{
			Rectangle rect = getBoundsAt(0);
			m_lineRect.setRect(-LINEWIDTH / 2, rect.y, LINEWIDTH, rect.height);
		}
		else if (next == getTabCount())
		{
			Rectangle rect = getBoundsAt(getTabCount() - 1);
			m_lineRect.setRect(rect.x + rect.width - LINEWIDTH / 2, rect.y,
					LINEWIDTH, rect.height);
		}
		else
		{
			Rectangle rect = getBoundsAt(next - 1);
			m_lineRect.setRect(rect.x + rect.width - LINEWIDTH / 2, rect.y,
					LINEWIDTH, rect.height);
		}
	}

	private void initTargetTopBottomLine(int next, TabTransferData a_data)
	{
		if (next < 0)
		{
			m_lineRect.setRect(0, 0, 0, 0);
			return;
		} // if

		if ((a_data.getTabbedPane() == this)
				&& (a_data.getTabIndex() == next || next - a_data.getTabIndex() == 1))
		{
			m_lineRect.setRect(0, 0, 0, 0);
		}
		else if (getTabCount() == 0)
		{
			m_lineRect.setRect(0, 0, 0, 0);
			return;
		}
		else if (next == getTabCount())
		{
			Rectangle rect = getBoundsAt(getTabCount() - 1);
			m_lineRect.setRect(rect.x, rect.y + rect.height - LINEWIDTH / 2,
					rect.width, LINEWIDTH);
		}
		else if (next == 0)
		{
			Rectangle rect = getBoundsAt(0);
			m_lineRect.setRect(rect.x, -LINEWIDTH / 2, rect.width, LINEWIDTH);
		}
		else
		{
			Rectangle rect = getBoundsAt(next - 1);
			m_lineRect.setRect(rect.x, rect.y + rect.height - LINEWIDTH / 2,
					rect.width, LINEWIDTH);
		}
	}

	public interface TabAcceptor
	{
		boolean isDropAcceptable(MainTabbed a_component, int a_index);
	}

	private void initHandlers()
	{
		tileHandler = null;
		collisionHandler = null;

	}

	public void initDefaultHandler(Window window)
	{
		selectHandler = new SelectHandler(this);
		// Default handler
		getSelectedMain().addHandler(selectHandler);
		window.addWindowFocusListener((WindowFocusListener) selectHandler);
		getSelectedMain().repaint();
	}

	public void safeRemove(int i)
	{
		super.remove(i);
	}

	private void initSelectHandler()
	{
		SelectHandlerAction tileAction = new SelectHandlerAction(
				SelectHandlerAction.TILE_HANDLER, this);
		SelectHandlerAction collisionAction = new SelectHandlerAction(
				SelectHandlerAction.COLLISION_HANDLER, this);
		SelectHandlerAction selectAction = new SelectHandlerAction(
				SelectHandlerAction.SELECT_HANDLER, this);
		SelectHandlerAction objectAction = new SelectHandlerAction(
				SelectHandlerAction.OBJECT_HANDLER, this);

		this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke(KeyEvent.VK_T, 0), "SELECT_TILE");
		this.getActionMap().put("SELECT_TILE", tileAction);

		this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke(KeyEvent.VK_E, 0), "SELECT_COLLISION");
		this.getActionMap().put("SELECT_COLLISION", collisionAction);

		this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), "SELECT_SELECT");
		this.getActionMap().put("SELECT_SELECT", selectAction);

		this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke(KeyEvent.VK_G, 0), "SELECT_OBJECT");
		this.getActionMap().put("SELECT_OBJECT", objectAction);
	}

	private int produceNumber()
	{
		int num = 1;
		int max = 1;
		ArrayList<Integer> nums = new ArrayList<Integer>();
		for (Component component : getComponents())
		{
			if (component instanceof MainScroll)
			{
				Main main = ((MainScroll) component).getMain();
				String name = main.getName();
				if (name.length() > 12)
				{
					if ((name.substring(0,
							StateHandler.DEFAULT_FILENAME.length()
									+ StateHandler.UNMODIFIED_PREFIX.length())
							.equals(StateHandler.UNMODIFIED_PREFIX
									+ StateHandler.DEFAULT_FILENAME))
							|| (name.substring(
									0,
									StateHandler.DEFAULT_FILENAME.length()
											+ StateHandler.MODIFIED_PREFIX
													.length())
									.equals(StateHandler.MODIFIED_PREFIX
											+ StateHandler.DEFAULT_FILENAME))
							&& name.substring(
									name.length()
											- StateHandler.DEFAULT_EXTENSION
													.length(),
									name.length()).equals(
									StateHandler.DEFAULT_EXTENSION))
					{
						int toAdd = main.isDirty() ? new Integer(
								Integer.parseInt(name.substring(
										StateHandler.DEFAULT_FILENAME.length()
												+ StateHandler.MODIFIED_PREFIX
														.length(),
										name.length()
												- StateHandler.DEFAULT_EXTENSION
														.length())))
								: new Integer(
										Integer.parseInt(name.substring(
												StateHandler.DEFAULT_FILENAME
														.length()
														+ StateHandler.UNMODIFIED_PREFIX
																.length(),
												name.length()
														- StateHandler.DEFAULT_EXTENSION
																.length())));
						nums.add(toAdd);
						max = Math.max(max, toAdd);
					}
				}
			}
		}
		for (int i = num; i <= max; i++)
		{
			for (int j : nums)
			{
				if (j == i && j == num)
					num++;
			}
		}
		return num;
	}

	private static final int LINEWIDTH = 3;
	private Desktop desktop;
	private final Rectangle2D m_lineRect = new Rectangle2D.Double();
	private static final String NAME = "TabTransfer";
	private TabAcceptor m_acceptor = null;
	private final DataFlavor FLAVOR = new DataFlavor(
			DataFlavor.javaJVMLocalObjectMimeType, NAME);
	private TileHandler tileHandler;
	private CollisionHandler collisionHandler;
	private SelectHandler selectHandler;
	private ObjectHandler objectHandler;
}
